import { type Range } from './index'

export default class VirtualList {
  sizeMaps: Map<string, number> = new Map<string, number>() // 存储元素的尺寸的Map对象
  offset: number = 0

  // 虚拟滚动核心参数配置
  param: {
    // 头部插槽的高度，用于计算滚动偏移量时的修正值
    slotHeaderSize: number
    // 底部插槽的高度，同样用于滚动计算的修正
    slotFooterSize: number
    // 可视区域内保持渲染的元素数量，决定了同时渲染在DOM中的元素个数
    keeps: number
    // 元素的预估高度，用于在未测量实际高度前的计算(用于计算前后padding需要填充的高度)
    estimateSize: number
    // 缓冲区大小，用于提前渲染临近区域的元素，避免快速滚动时出现白屏
    buffer: number
    // 数据源中每个元素的唯一标识数组，用于标识和追踪每个列表项
    uniqueIds: string[]
  } | null = null

  // 更新回调函数，当渲染范围发生变化时调用
  callUpdate: ((rangePara: Range) => void) | null = null
  // 第一个范围的总尺寸，用于计算平均尺寸以估算未渲染元素高度
  firstRangeTotalSize: number = 0
  // 第一个范围的平均尺寸，用于估算未渲染元素的尺寸
  firstRangeAverageSize: number = 0
  // 上一次计算的索引位置，用于优化索引偏移量计算
  lastCalcIndex: number = 0
  // 固定尺寸值，在FIXED模式下使用，存储所有元素的统一高度
  fixedSizeValue: number = 0
  // 计算类型：INIT(初始化) | FIXED(固定高度) | DYNAMIC(动态高度)
  calcType: string = 'INIT'
  // 滚动方向：FRONT(向前滚动) | BEHIND(向后滚动)
  direction: string = ''
  // 当前渲染范围信息
  range: Range = Object.create(null)

  constructor(param: any, callUpdate: (rangePara: Range) => void) {
    this.init(param, callUpdate)
  }

  /**
   * 初始化
   * @param {object} param - 参数对象
   * @param {function} callUpdate - 更新回调函数
   */
  init(param: any, callUpdate: (rangePara: Range) => void) {
    this.param = param
    this.callUpdate = callUpdate

    this.sizeMaps = new Map()
    this.firstRangeTotalSize = 0
    this.firstRangeAverageSize = 0
    this.lastCalcIndex = 0
    this.fixedSizeValue = 0
    this.calcType = 'INIT'

    this.offset = 0
    this.direction = ''

    this.range = Object.create(null)
    if (param) {
      this.changeStartEndRange(0, param.keeps - 1)
    }
  }

  /**
   * 获取当前范围
   * @returns {object} - 当前范围对象
   */
  getRange() {
    const range = Object.create(null)
    range.start = this.range.start
    range.end = this.range.end
    range.padFront = this.range.padFront
    range.padBehind = this.range.padBehind
    return range
  }

  // 销毁整个虚拟列表实例
  destroy() {
    this.init(null, () => {})
  }

  isFront() {
    return this.direction === 'front'
  }

  isBehind() {
    return this.direction === 'behind'
  }

  getOffsetByIndex(index: number) {
    return index === 0 ? 0 : this.getCurrentIndexOffset(index) + this.getEstimateSize()
  }

  //处理滚动条滚动事件
  handleScroll(scrollTop: number) {
    //向前滚动还是向后滚动
    this.direction = scrollTop < this.offset ? 'FRONT' : 'BEHIND'
    this.offset = scrollTop

    //判断是否初始化参数
    if (!this.param) {
      return
    }

    //向前或者向后进行数据处理
    if (this.direction === 'FRONT') {
      this.handleScrollFront(scrollTop)
    } else if (this.direction === 'BEHIND') {
      this.handleScrollBehind(scrollTop)
    }
  }

  //向前滚动处理,更新前后范围，和上下填充的padding
  handleScrollFront(scrollTop: number) {
    //获取当前滚动条的下标位置（的前一个位置或者如果是0则获取的0下标）
    const index = this.getRangeIndexByOffset(scrollTop)

    //获取前范围下标位置
    const start = Math.max(0, index - (this.param!.buffer || 0))

    const end = this.getEndIndexByStartIndex(start)

    //更新渲染页面数据范围
    this.changeStartEndRange(start, end)
  }

  //向后滚动处理 ,更新前后范围，和上下填充的padding
  handleScrollBehind(scrollTop: number) {
    //获取当前滚动条的下标位置（的前一个位置或者如果是0则获取的0下标）
    const index = this.getRangeIndexByOffset(scrollTop)

    if (index < this.range.start + (this.param?.buffer || 0)) {
      return
    }

    //获取前范围下标位置
    const start = Math.max(0, index - (this.param!.buffer || 0))

    const end = this.getEndIndexByStartIndex(start)

    //更新渲染页面数据范围
    this.changeStartEndRange(start, end)
  }

  //跟新渲染范围数据
  changeStartEndRange(start: number, end: number) {
    const keeps = this.param?.keeps || 0
    const totalLength = this.param?.uniqueIds.length || 0
    if (keeps >= totalLength) {
      start = 0
      end = this.getLastIndex()
    }

    if (start != this.range.start) {
      this.updateRange(start, end)
    }
  }

  getEndIndexByStartIndex(start: number) {
    //理论位置
    const computeEnd = start + (this.param?.keeps || 0) + (this.param?.buffer || 0)
    //理论位置不能超过最后索引
    const end = Math.min(computeEnd, this.getLastIndex())
    // 返回计算的结束下标
    return end
  }

  //更新父组件数据
  updateRange(start: number, end: number) {
    this.range.start = start
    this.range.end = end
    this.range.padFront = this.getPadFront(start)
    this.range.padBehind = this.getPadBehind(end)
    if (this.callUpdate) {
      this.callUpdate(this.range)
    }
  }

  //计算填充的底部高度
  getPadBehind(end: number) {
    const lastIndex = this.getLastIndex()
    if (this.calcType === 'FIXED') {
      return (lastIndex - end) * this.fixedSizeValue
    } else {
      if (this.lastCalcIndex == lastIndex) {
        //      //当 lastCalcIndex 等于 lastIndex 时，说明已经计算过最后一个元素的偏移量
        //       // 直接使用已计算的精确值，避免使用估算值，提高准确性

        //// 场景1: 滚动到底部后计算padding
        // // lastCalcIndex 被更新为 99
        // // 当计算 getPadBehind() 时，条件成立
        //
        // // 场景2: 在底部附近反复滚动
        // // lastCalcIndex 保持为 99
        // // 条件持续成立，使用精确计算
        return this.getCurrentIndexOffset(lastIndex) - this.getCurrentIndexOffset(end)
      } else {
        //使用估算值计算后面没有渲染填充的高度值（因为只有渲染过的值才能准确获取元素高度）
        return (lastIndex - end) * this.getEstimateSize()
      }
    }
  }

  //计算填充的顶部高度
  getPadFront(start: number) {
    if (this.calcType === 'FIXED') {
      return start * this.fixedSizeValue
    } else {
      return this.getCurrentIndexOffset(start)
    }
  }

  //获取当前下表的位置大小（没有渲染的数据使用估算进行计算）
  getRangeIndexByOffset(scrollTop: number): number {
    const realScrollTop = scrollTop - (this.param?.slotHeaderSize || 0)
    if (realScrollTop <= 0) {
      return 0
    }

    //当前虚拟列表为固定高度
    if (this.calcType === 'FIXED') {
      return Math.ceil(realScrollTop / this.fixedSizeValue)
    }

    if (!this.param) {
      return 0
    }

    //使用二分查找获取当前滚动条滚动到的对应的下标位置
    let start = 0
    let end = this.param.uniqueIds.length - 1
    let middle = 0
    while (start <= end) {
      //中间位置下标
      middle = Math.floor((start + end) / 2)
      // 获取当前下标位置的偏移量
      const offset = this.getCurrentIndexOffset(middle)
      if (offset === realScrollTop) {
        return middle
      } else if (offset < realScrollTop) {
        start = middle + 1
      } else {
        end = middle - 1
      }
    }

    return start > 0 ? --start : 0
  }

  getCurrentIndexOffset(currentIndex: number) {
    if (!this.param) {
      return 0
    }
    let offset = 0
    for (let i = 0; i < currentIndex; i++) {
      const value = this.sizeMaps.get(this.param.uniqueIds[i])
      if (typeof value === 'number') {
        offset += value
      } else {
        offset += this.getEstimateSize()
      }
    }

    //获取最后一次计算的索引位置
    this.lastCalcIndex = Math.max(this.lastCalcIndex, currentIndex - 1)
    this.lastCalcIndex = Math.min(this.lastCalcIndex, this.getLastIndex())

    return offset
  }

  //获取数据的长度的大小的最后一个索引位置
  getLastIndex() {
    return this.param ? this.param.uniqueIds.length - 1 : 0
  }

  //获取固定长度大小
  getEstimateSize() {
    //获取没有被保存的元素Item的尺寸大小
    return this.calcType === 'FIXED' ? this.fixedSizeValue : this.param?.estimateSize || 50
  }

  saveSize(key: string, height: number) {
    this.sizeMaps.set(key, height)
  }

  updateParam(key: string, value: any) {
    if (this.param && key in this.param) {
      if (key === 'uniqueIds') {
        this.sizeMaps.forEach((v, key) => {
          if (!value.includes(key)) {
            this.sizeMaps.delete(key)
          }
        })
      }
      this.param[key as keyof typeof this.param] = value
    }
  }

  handleDataChange() {
    if (this.param) {
      const direction = this.direction
      let start = this.range.start
      if (direction === 'FRONT') {
        start = start - 2
      } else {
        start = start + 2
      }

      const end = this.getEndIndexByStartIndex(start)

      //更新渲染页面数据范围
      this.changeStartEndRange(start, end)
    }
  }
}
